#pragma once

#include <iostream>
#include <type_traits>

#define NB_ELEMENTS 10
#define MAX_SIZE_FOR_COPY 8

template <class Type>
class List
{
private: 
	Type* list;
	int length;
	int max;
	bool isPtr;
	bool del;
	void CopyArray(const Type* const, Type* const, int);
	void DeleteAll();
public:
	List();
	List(int);
	~List();
	int GetLength() const { return length; }
	int GetLastIndex() const { return length - 1; }
	void Add(Type&);
	void Add(Type&, int);
	void Remove(int);
	void Push(Type&);
	void Pop();
	void PopFirst();
	int FindFirstOf(const Type&) const;
	int FindLastOf(const Type&) const;
	void DoDelete();
	void DontDelete();
	void Display() const;

	Type operator[](int) const;
	List<Type>& operator+=(Type&);
	List<Type>& operator-=(Type&);
};

template <class Type>
List<Type>::List() :
list(0),
length(0),
max(NB_ELEMENTS),
isPtr(false), 
del(false) {
	list = new Type[max];
	isPtr = std::is_pointer<Type>::value;
	del = isPtr;
}

template <class Type>
List<Type>::List(int nb) :
list(0),
length(0),
max(nb),
isPtr(false), 
del(false) {
	list = new Type[max];
	isPtr = std::is_pointer<Type>::value;
	del = isPtr;
}

template <class Type>
void List<Type>::CopyArray(const Type* const src, Type* const dest, int nb) {
	for (int i = 0; i < nb; i++) {
		dest[i] = src[i];
	}

	return;
}

template <class Type>
void List<Type>::Add(Type& element) {
	Add(element, -1);

	return;
}

template <class Type>
void List<Type>::Add(Type& element, int index) {
	if (index < 0) {
		index = length;
	}
	
	if (length >= max) {
		// copy the list into a temp one
		Type* tempList = new Type[length];
		CopyArray(list, tempList, length);

		// delete and reinitialize the list
		delete[] list;
		list = 0;

		// increment the max for the new list
		max *= 2;

		// create the new list
		list = new Type[max];
		CopyArray(tempList, list, length);

		delete[] tempList;
	}

	// move elements from the index to be inserted
	for (int i = index; i < length; i++) {
		list[i + 1] = list[i];
	}

	// insert the new element, by copy if small enough, by reference otherwise
	if (sizeof(element) <= MAX_SIZE_FOR_COPY) {
		Type e = element;
		list[index] = e;
	} else {
		list[index] = element;
	}

	// increment the length of the list
	length++;

	return;
}

template <class Type>
void List<Type>::Remove(int index) {
	length--;

	if (isPtr && del) {
		delete list[index];
	}

	for (int i = index; i < length; i++) {
		list[i] = list[i + 1];
	}

	return;
}

template <class Type>
void List<Type>::Push(Type& element) {
	if (length >= max) {
		// copy the list into a temp one
		Type* tempList = new Type[length];
		CopyArray(list, tempList, length);

		// delete and reinitialize the list
		delete[] list;
		list = 0;

		// increment the max for the new list
		max *= 2;

		// create the new list
		list = new Type[max];
		CopyArray(tempList, list, length);

		delete[] tempList;
	}

	// insert the new element at the end of the list, by copy if small enough, by reference otherwise
	if (sizeof(element) <= MAX_SIZE_FOR_COPY) {
		Type e = element;
		list[length] = e;
	}
	else {
		list[length] = element;
	}

	// increment the length of the list
	length++;

	return;
}

template <class Type>
void List<Type>::Pop() {
	length--;

	if (isPtr && del) {
		delete list[length];
	}

	return;
}

template <class Type>
void List<Type>::PopFirst() {
	length--;

	if (isPtr && del) {
		delete list[0];
	}

	for (int i = 0; i < length; i++) {
		list[i] = list[i + 1];
	}

	return;
}

/*
Returns -1 if no instances found.
*/
template <class Type>
int List<Type>::FindFirstOf(const Type& element) const {
	for (int i = 0; i < length; i++) {
		if (list[i] == element) {
			return i;
		}
	}

	return -1;
}

/*
Returns -1 if no instances found.
*/
template <class Type>
int List<Type>::FindLastOf(const Type& element) const {
	for (int i = length - 1; i >= 0; i--) {
		if (list[i] == element) {
			return i;
		}
	}

	return -1;
}

template <class Type>
void List<Type>::DoDelete() {
	del = true;

	return;
}

template <class Type>
void List<Type>::DontDelete() {
	del = false;

	return;
}

template <class Type>
void List<Type>::Display() const {
	for (int i = 0; i < length; i++) {
		std::cout << list[i] << std::endl;
	}
	return;
}

template <class Type>
void List<Type>::DeleteAll() {
	for (int i = 0; i < length; i++) {
		delete list[i];
	}
}

template <class Type>
Type List<Type>::operator[](int i) const {
	return *(list + i);
}

template <class Type>
List<Type>& List<Type>::operator+=(Type& element) {
	Push(element);

	return *this;
}

template <class Type>
List<Type>& List<Type>::operator-=(Type& element) {
	int i = FindFirstOf(element);
	Remove(i);

	return *this;
}

template <class Type>
List<Type>::~List() {
	if (isPtr && del) {
		DeleteAll();
	}
	
	delete[] list;
}
